package com.inepex.inegraphics.shared;

import java.util.ArrayList;
import java.util.Collections;

import com.inepex.inegraphics.shared.gobjects.Circle;
import com.inepex.inegraphics.shared.gobjects.EllipticArc;
import com.inepex.inegraphics.shared.gobjects.GraphicalObject;
import com.inepex.inegraphics.shared.gobjects.Line;
import com.inepex.inegraphics.shared.gobjects.LineTo;
import com.inepex.inegraphics.shared.gobjects.MoveTo;
import com.inepex.inegraphics.shared.gobjects.Path;
import com.inepex.inegraphics.shared.gobjects.PathElement;
import com.inepex.inegraphics.shared.gobjects.QuadraticCurveTo;
import com.inepex.inegraphics.shared.gobjects.Rectangle;
import com.inepex.inegraphics.shared.gobjects.Text;

/**
 * 
 * An abstract class that representing a drawing area (2d surface).
 * Contains helper methods for drawing and also stores the 
 * {@link GraphicalObject}s and the {@link Context}s used during the drawing process.
 * It uses its own shadowing process (by default).
 * 
 * Implementation subclasses may provide further features, depending on their environment and
 * developing phase.
 * 
 * @author Miklós Süveges / Inepex Ltd.
 *
 */
public abstract class DrawingArea extends GraphicalObjectContainer{
	
	protected int width;
	protected int height;
	/**
	 * Set false in implementation if it fully supports shadowDrawing
	 * If true the Shadows will be drawn before {@link GraphicalObject}s per zIndex just under the go's
	 */
	protected boolean createShadows = true;

	protected Context actualContext;

	
	protected DrawingArea() {
		this(0,0);
	}
	
	/**
	 * Constructs an empty surface with default {@link Context}
	 * @param width
	 * @param height
	 */
	protected DrawingArea(int width, int height) {
		this.width = width;
		this.height = height;
		actualContext = new Context();
	}

	/**
	 * Constructs an empty surface with the given initialContext (it will be the first -> 0.)
	 * @param width
	 * @param height
	 * @param initialContext
	 */
	protected DrawingArea(int width, int height, Context initialContext) {
		this.width = width;
		this.height = height;
		actualContext = initialContext;
	}

	/**
	 * 
	 * Updates / (re)draws all {@link GraphicalObject}s 
	 *  
	 */
	public void update(){
		Collections.sort(graphicalObjects, GraphicalObject.getZComparator());
		clear();
		for (GraphicalObject go : createDrawOrderList()) {
			drawGraphicalObject(go);
		}
	}
	
	protected ArrayList<GraphicalObject> createDrawOrderList(){
		ArrayList<GraphicalObject> orderedGOs = new ArrayList<GraphicalObject>();
		if(graphicalObjects.size() == 0){
			return orderedGOs;
		}
		Collections.sort(graphicalObjects, GraphicalObject.getZComparator()); //zIndex sort
		ArrayList<GraphicalObject> gos = new ArrayList<GraphicalObject>();
		ArrayList<GraphicalObject> shadows = new ArrayList<GraphicalObject>();
		int actualzIndex = graphicalObjects.get(0).getzIndex();
		for (GraphicalObject go : graphicalObjects) {
			if(go.getContext() == null){
				continue;
			}
			if(go.getzIndex() != actualzIndex){
				orderedGOs.addAll(shadows);
				orderedGOs.addAll(gos);
				shadows.clear();
				gos.clear();
				actualzIndex = go.getzIndex();
			}
			if(createShadows && go.getContext().hasShadow()){
				shadows.add(createGraphicalObjectShadow(go));
			}
			gos.add(go);
		}
		orderedGOs.addAll(shadows);
		orderedGOs.addAll(gos);

		return orderedGOs;
	}

	protected void drawGraphicalObject(GraphicalObject go){
		if(go instanceof Line){
			drawLine((Line) go);
		}
		else if(go instanceof Circle){
			drawCircle((Circle) go);
		}
		else if(go instanceof Rectangle){
			drawRectangle((Rectangle) go);
		}
		else if(go instanceof Path){
			drawPath((Path) go);
		}
		else if(go instanceof Text){
			drawText((Text) go);
		}
		else if(go instanceof EllipticArc){
			drawEllipticArc((EllipticArc) go);
		}

	}
	
	protected GraphicalObject createGraphicalObjectShadow(GraphicalObject go){
		Context context = new Context(
				go.getContext().shadowAlpha,
				go.getContext().shadowColor,
				go.getContext().strokeWidth,
				go.getContext().shadowColor
				);
		GraphicalObject shadow = null;
		if(go instanceof Line){
			shadow = new Line(
					go.getBasePointX() + go.getContext().getShadowOffsetX(),
					go.getBasePointY() + go.getContext().getShadowOffsetY(),
					((Line) go).getEndPointX() + go.getContext().getShadowOffsetX(),
					((Line) go).getEndPointX() + go.getContext().getShadowOffsetY(),
					context, go.getzIndex());
		}
		else if(go instanceof Circle){
			shadow = new Circle(
					go.getBasePointX() + go.getContext().getShadowOffsetX(),
					go.getBasePointY() + go.getContext().getShadowOffsetY(),
					((Circle) go).getRadius(), go.hasStroke(),
					go.hasFill(), context,
					go.getzIndex());
		}
		else if(go instanceof Rectangle){
			shadow = new Rectangle(
					go.getBasePointX() + go.getContext().getShadowOffsetX(),
					go.getBasePointY() + go.getContext().getShadowOffsetY(),
					((Rectangle) go).getWidth(), ((Rectangle) go).getHeight(),
					((Rectangle) go).getRoundedCornerRadius(),
					go.hasStroke(), go.hasFill(),	context, go.getzIndex());
		}
		else if(go instanceof Path){
			shadow = new Path(
					go.getBasePointX() + go.getContext().getShadowOffsetX(),
					go.getBasePointY() + go.getContext().getShadowOffsetY(),
					go.hasStroke(), go.hasFill(),
					go.getzIndex(), context);
			for(PathElement pe :((Path) go).getPathElements()){
				PathElement pathElementShadow = null;
				if(pe instanceof QuadraticCurveTo){
					QuadraticCurveTo qTo = (QuadraticCurveTo) pe;
					pathElementShadow = new QuadraticCurveTo(
							qTo.getEndPointX() + go.getContext().getShadowOffsetX(),
							qTo.getEndPointY() + go.getContext().getShadowOffsetY(),
							qTo.getControlPointX() + go.getContext().getShadowOffsetX(),
							qTo.getControlPointY() + go.getContext().getShadowOffsetY());
				}
				else if(pe instanceof LineTo){
					LineTo lTo = (LineTo) pe;
					pathElementShadow = new LineTo(
							lTo.getEndPointX() + go.getContext().getShadowOffsetX(),
							lTo.getEndPointY() + go.getContext().getShadowOffsetY());
				}
				else if(pe instanceof MoveTo){
					MoveTo mTo = (MoveTo) pe;
					pathElementShadow = new MoveTo(
							mTo.getEndPointX() + go.getContext().getShadowOffsetX(),
							mTo.getEndPointY() + go.getContext().getShadowOffsetY());
				}
				((Path) shadow).getPathElements().add(pathElementShadow);
			}
		}
		else if(go instanceof EllipticArc){
			shadow = new EllipticArc(
					go.getBasePointX() + go.getContext().getShadowOffsetX(),
					go.getBasePointY() + go.getContext().getShadowOffsetY(),
					((EllipticArc) go).getHorizontalRadius(), 
					((EllipticArc) go).getVerticalRadius(),
					((EllipticArc) go).getStartAngle(), 
					((EllipticArc) go).getArcAngle(),
					go.hasFill(), go.hasStroke(), context, go.getzIndex(), ((EllipticArc) go).getArcShape());
		}
		return shadow;
	}

	protected void drawGraphicalObjectShadow(GraphicalObject go){
		drawGraphicalObject(createGraphicalObjectShadow(go));
	}

	/**
	 * 
	 * Clears the canvas
	 */
	protected abstract void clear();

	protected abstract void drawPath(Path path);

	protected abstract void drawRectangle(Rectangle rectangle);

	protected abstract void drawCircle(Circle circle);

	protected abstract void drawLine(Line line);

	protected abstract void drawEllipticArc(EllipticArc arc);

	protected abstract void drawText(Text text);

	/**
	 * @return the width
	 */
	public int getWidth() {
		return width;
	}

	/**
	 * @return the height
	 */
	public int getHeight() {
		return height;
	}

	/**
	 * @return the createShadows
	 */
	public boolean isCreateShadows() {
		return createShadows;
	}

	/**
	 * @param createShadows the createShadows to set
	 */
	public void setCreateShadows(boolean createShadows) {
		this.createShadows = createShadows;
	}

	public abstract void measureText(Text text);
}
